//索引
#include<iostream>
#include<string>
#include<vector>
#include<fstream>
#include<unordered_map>
#include<mutex>
#include"util.hpp"
#include"log.hpp"
namespace ns_index
{
    struct DocInfo
    {
        std::string title;//文档的标题
        std::string content;//文档去标签的内容
        std::string url;//官网文档url
        uint64_t doc_id;//文档id
    };
    //倒排拉链结构
    struct InvertedElem
    {
        uint64_t doc_id;
        std::string word;
        int weight;
        InvertedElem():weight(0){}
    };
    //倒排拉链
    typedef std::vector<InvertedElem> InvertedList;

    class Index//索引
    {
        private:
        //正排索引的数据结构用数组，数组的下标是文档id
        std::vector<DocInfo> forward_index;//正排索引
        //倒排索引一定是一个关键字和一组Invertedelem对应关键字和倒排拉链的映射关系
        std::unordered_map<std::string ,InvertedList> inverted_index;//映射
        private:
        Index()
        {}
        Index(const Index&)=delete;//禁止拷贝
        Index& operator=(const Index)=delete;//禁止复制
        
        static Index* instance;
        static std::mutex mtx;//锁
        public:
        ~Index()
        {}
        public:
        static Index* GetInstance()//获取实例
        {
            if(nullptr == instance)//判断
            {
               mtx.lock();
               if(nullptr==instance)
               {
                instance = new Index();
               }
               mtx.unlock();
            }
             return instance;
        }
        //根据doc_id找到文档内容
       DocInfo *GetForwardIndex(uint64_t doc_id)
       {
            if(doc_id>=forward_index.size())
            {
                std::cerr<<"doc_id out range error!"<<std::endl;
                return nullptr;
            }
            return &forward_index[doc_id];
       }
       //根据关键字获得倒排拉链
       InvertedList* GetInvertedList(const std::string &word)
       {
        auto iter = inverted_index.find(word);//关键字
        if(iter ==inverted_index.end())
        {
            std::cerr<<word<<"have no InvertedList"<<std::endl;
            return nullptr;
        }
        return &(iter->second);
       }
       //去标签，和石化之后的文档，构建征袍和倒排索引
       //data/raw_html/raw/txt
       bool BuildIndex(const std::string &input)//parse处理完毕的交给input
       {    
            std::ifstream in(input,std::ios::in | std::ios::binary);
            if(!in.is_open())
            {
                std::cerr<<"sorry"<<input<<"open error"<<std::endl;
                return false;
            }
            //输入计数关键字次数
            std::string line;
            int count = 0;
            while (std::getline(in,line))
            {
                DocInfo* doc=BuildForwardIndex(line);//构建正排索引
                if(doc==nullptr)
                {
                    std::cerr<<"build"<<line<<"error"<<std::endl;
                    continue;
                }
                BuildInvertedIndex(*doc);//构建倒叙索引
                count++;
                LOG(NORMAL,"当前已经练级的索引文档："+std::to_string(count));
            }
            return true;
       }
       private:
       //正排索引
       DocInfo *BuildForwardIndex(const std::string &line)
       {
            //解析line 字符串切分
            std::vector<std::string> results;
            const std::string sep = "\3";//行内分隔符
            ns_util::StringUtil::Split(line,&results,sep);
            if(results.size()!=3)
            {
                return nullptr;
            }
            //字符串填充到DocInfo
            DocInfo doc;
            doc.title = results[0];
            doc.content = results[1];
            doc.url = results[2];
            doc.doc_id = forward_index.size();//保存ip，在插入，对应的ip就是当前doc在vector中的下标
            //插入大豆正排索引
            forward_index.push_back(std::move(doc));//html文件内容
            return &forward_index.back();
       }
       //构建倒排索引
       bool BuildInvertedIndex(const DocInfo &doc)
       {
            //标题，内容 url 文档id 
            //word->倒排索引
            struct word_cnt
            {
                int title_cnt;
                int content_cnt;
                word_cnt():title_cnt(0),content_cnt(0){}
            };
            std::unordered_map<std::string ,word_cnt> word_map;//暂存词频的映射表
           //对标题进行分词
           std::vector<std::string >title_words;
           ns_util::JiebaUtil::CutString(doc.title,&title_words);
           //对标题进行此音统计
           for(std::string s:title_words)
           {
             boost::to_lower(s);//统一转化为小写
             word_map[s].title_cnt++;//存在就获取，不存在就新建

           } 
          //对文档内容进行分词
          std::vector<std::string> content_words;
          ns_util::JiebaUtil::CutString(doc.content,&content_words);

          //对内容进行词频统计
          for(std::string s:content_words)
          {
            boost::to_lower(s);
            word_map[s].content_cnt++;
          }
#define X 10
#define Y 1
            for(auto &word_pair :word_map)
            {
                InvertedElem item;
                item.doc_id = doc.doc_id;
                item.word = word_pair.first;
                item.weight = X*word_pair.second.title_cnt+Y*word_pair.second.content_cnt;//相关性
                InvertedList &inverted_list = inverted_index[word_pair.first];
                inverted_list.push_back(std::move(item));
            }
            return true;
       }
    };
    Index* Index::instance = nullptr;
    std::mutex Index::mtx;
}
